import torch

# 对函数y = 2x⊤x关于列向量x求导
x = torch.arange(4.0)
x.requires_grad_(True) # 相当于x = torch.arange(4.0, requires_grad_=True)
y = 2 * torch.dot(x, x)
y
y.backward() # 使用反向传播函数进行变量自动求导
x.grad # 输出自变量的梯度

# 计算x的另一个函数：y = sum(x)
x.grad.zero_() #默认情况下，pytorch会累积梯度，我们需要清除之前的值
y = sum(x)
y.backward()
x.grad

# 以上的y输出都是标量，当y不是标量时，向量y关于向量x的导数的最自然解释是一个矩阵
# 对非标量调用backward需要传入一个gradient参数，该参数指定微分函数关于self的梯度。
# 本例只想求偏导数的和，所以传递一个1的梯度是合适的
x.grad.zero_()
y = x * x
y.sum().backward() # 相当于y.backward(torch.ones(len(x))
x.grad

# 分离计算：即分布求导，中间变量看作常数进行，如：z=u*x，u=x*x
x.grad.zero_()
y = x * x
u = y.detach() # u中间变量，最外层求导时看成常数
z = u * x
z.sum().backward()
x.grad == u
# 由于记录了y的计算结果，我们可以随后在y上调用反向传播，得到y=x*x关于的x的导数，即2*x
x.grad.zero_() # 如果梯度不清零，计算结果则将是错误的
y.sum().backward()
x.grad == 2*x

# 同样在控制流中也能实现自动求导功能，如：
def f(a):
    b = a * 2
    while b.norm() < 1000:
        b = b * 2
    if b.sum() > 0:
        c = b
    else:
        c = 100 * b
    return c
a = torch.randn(size=(), requires_grad=True) # 随机生成一个标量输入
d = f(a)
d.backward()
a.grad == d / a # 验证求导结果是否准确

